<!DOCTYPE HTML>
<html lang="en">
<meta charset="utf-8">
<title>Blue Robot Demo</title>
<style>
  html { overflow: hidden; min-height: 200px; min-width: 380px; }
  body { height: 200px; position: relative; margin: 8px; }
  .buttons { position: absolute; bottom: 0px; left: 0px; margin: 4px; }
</style>
<canvas width="380" height="200"></canvas>
<script>
 var Landscape = function (context, width, height) {
   this.offset = 0;
   this.width = width;
   this.advance = function (dx) {
     this.offset += dx;
   };
   this.horizon = height * 0.7;
   // This creates the sky gradient (from a darker blue to white at the bottom)
   this.sky = context.createLinearGradient(0, 0, 0, this.horizon);
   this.sky.addColorStop(0.0, 'rgb(55,121,179)');
   this.sky.addColorStop(0.7, 'rgb(121,194,245)');
   this.sky.addColorStop(1.0, 'rgb(164,200,214)');
   // this creates the grass gradient (from a darker green to a lighter green)
   this.earth = context.createLinearGradient(0, this.horizon, 0, height);
   this.earth.addColorStop(0.0, 'rgb(81,140,20)');
   this.earth.addColorStop(1.0, 'rgb(123,177,57)');
   this.paintBackground = function (context, width, height) {
     // first, paint the sky and grass rectangles
     context.fillStyle = this.sky;
     context.fillRect(0, 0, width, this.horizon);
     context.fillStyle = this.earth;
     context.fillRect(0, this.horizon, width, height-this.horizon);
     // then, draw the cloudy banner
     // we make it cloudy by having the draw text off the top of the
     // canvas, and just having the blurred shadow shown on the canvas
     context.save();
     context.translate(width-((this.offset+(this.width*3.2)) % (this.width*4.0))+0, 0);
     context.shadowColor = 'white';
     context.shadowOffsetY = 30+this.horizon/3; // offset down on canvas
     context.shadowBlur = '5';
     context.fillStyle = 'white';
     context.textAlign = 'left';
     context.textBaseline = 'top';
     context.font = '20px sans-serif';
     context.fillText('WHATWG ROCKS', 10, -30); // text up above canvas
     context.restore();
     // then, draw the background tree
     context.save();
     context.translate(width-((this.offset+(this.width*0.2)) % (this.width*1.5))+30, 0);
     context.beginPath();
     context.fillStyle = 'rgb(143,89,2)';
     context.lineStyle = 'rgb(10,10,10)';
     context.lineWidth = 2;
     context.rect(0, this.horizon+5, 10, -50); // trunk
     context.fill();
     context.stroke();
     context.beginPath();
     context.fillStyle = 'rgb(78,154,6)';
     context.arc(5, this.horizon-60, 30, 0, Math.PI*2); // leaves
     context.fill();
     context.stroke();
     context.restore();
   };
   this.paintForeground = function (context, width, height) {
     // draw the box that goes in front
     context.save();
     context.translate(width-((this.offset+(this.width*0.7)) % (this.width*1.1))+0, 0);
     context.beginPath();
     context.rect(0, this.horizon - 5, 25, 25);
     context.fillStyle = 'rgb(220,154,94)';
     context.lineStyle = 'rgb(10,10,10)';
     context.lineWidth = 2;
     context.fill();
     context.stroke();
     context.restore();
   };
 };
</script>
<script>
 var BlueRobot = function () {
   this.sprites = new Image();
   this.sprites.src = 'blue-robot.png'; // this sprite sheet has 8 cells
   this.targetMode = 'idle';
   this.walk = function () {
     this.targetMode = 'walk';
   };
   this.stop = function () {
     this.targetMode = 'idle';
   };
   this.frameIndex = {
     'idle': [0], // first cell is the idle frame
     'walk': [1,2,3,4,5,6], // the walking animation is cells 1-6
     'stop': [7], // last cell is the stopping animation
   };
   this.mode = 'idle';
   this.frame = 0; // index into frameIndex
   this.tick = function () {
     // this advances the frame and the robot
     // the return value is how many pixels the robot has moved
     this.frame += 1;
     if (this.frame >= this.frameIndex[this.mode].length) {
       // we've reached the end of this animation cycle
       this.frame = 0;
       if (this.mode != this.targetMode) {
         // switch to next cycle
         if (this.mode == 'walk') {
           // we need to stop walking before we decide what to do next
           this.mode = 'stop';
         } else if (this.mode == 'stop') {
           if (this.targetMode == 'walk')
             this.mode = 'walk';
           else
             this.mode = 'idle';
         } else if (this.mode == 'idle') {
           if (this.targetMode == 'walk')
             this.mode = 'walk';
         }
       }
     }
     if (this.mode == 'walk')
       return 8;
     return 0;
   },
   this.paint = function (context, x, y) {
     if (!this.sprites.complete) return;
     // draw the right frame out of the sprite sheet onto the canvas
     // we assume each frame is as high as the sprite sheet
     // the x,y coordinates give the position of the bottom center of the sprite
     context.drawImage(this.sprites,
                       this.frameIndex[this.mode][this.frame] * this.sprites.height, 0, this.sprites.height, this.sprites.height,
                       x-this.sprites.height/2, y-this.sprites.height, this.sprites.height, this.sprites.height);
   };
 };
</script>
<script>
 var animating = false;
 var canvas = document.getElementsByTagName('canvas')[0];
 var context = canvas.getContext('2d');
 var landscape = new Landscape(context, canvas.width, canvas.height);
 var blueRobot = new BlueRobot();
 // paint when the browser wants us to, using requestAnimationFrame()
 function paint() {
   context.clearRect(0, 0, canvas.width, canvas.height);
   landscape.paintBackground(context, canvas.width, canvas.height);
   blueRobot.paint(context, canvas.width/2, landscape.horizon*1.1);
   landscape.paintForeground(context, canvas.width, canvas.height);
   if (animating)
     requestAnimationFrame(paint);
 }
 var interval = null;
 var cancelingTimeout = null;
 function startAnim() {
   if (cancelingTimeout) {
     clearTimeout(cancelingTimeout);
     cancelingTimeout = null;
   }
   if (!animating) {
     animating = true;
     paint();
     // but tick every 100ms, so that we don't slow down when we don't paint
     interval = setInterval(function () {
       var dx = blueRobot.tick();
       landscape.advance(dx);
     }, 100);
   }
 }
 function stopAnim() {
   if (cancelingTimeout) return;
   cancelingTimeout = setTimeout(function () {
     cancelingTimeout = null;
     if (animating) {
       clearInterval(interval);
       animating = false;
     }
   }, 1000);
 }
 paint();
 blueRobot.sprites.onload = paint;
</script>
<p class="buttons">
 <input type=button value="Walk" onclick="blueRobot.walk(); startAnim();">
 <input type=button value="Stop" onclick="blueRobot.stop(); stopAnim();">
<footer>
 <small> Blue Robot Player Sprite by <a href="https://johncolburn.deviantart.com/">JohnColburn</a>.
 Licensed under the terms of the Creative Commons Attribution Share-Alike 3.0 Unported license.</small>
 <small> This work is itself licensed under a <a rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/">Creative
 Commons Attribution-ShareAlike 3.0 Unported License</a>.</small>
</footer>
